/* 
 * format - haXe File Formats
 *
 *  SWF File Format
 *  Copyright (C) 2004-2008 Nicolas Cannasse
 *
 * Copyright (c) 2008, The haXe Project Contributors
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package format.f4v;
import format.f4v.Data;
import format.flv.Data;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
class Tools 
{
	
	public static function f4v2flv(inFile):Null<{preTags:Array<format.flv.Data>, tags:Array<format.flv.Data>}>
	{
		if(inFile=="")
			return null;
		var inBytes = neko.io.File.getBytes(inFile);
		var bytesInput = new haxe.io.BytesInput(inBytes);
		var reader = new format.f4v.Reader(bytesInput);
		var f4v:F4V = reader.read(inBytes.length);
		return createFlvTags(f4v, inBytes);
	}
	static function getTraks(f4v:F4V)
	{
		var info:Dynamic=null;
		var traks:Array
		<{
				type:String,
				timeScale:Int,
				sampleDuration:Int,
				records:Array<StscRecord>, 
				chunkType:String,
				chunkOffsets:Array<Dynamic /*Int, Float */>, 
				samples:StszData,
				syncTable:Array<Int /*32 */ >
		}> = new Array();
		var trak:Dynamic=null;
		for(b in f4v.boxes)
		{
			switch(b)
			{
				case Moov(header, boxes):
					for(b in boxes)
					{
						switch(b)
						{
							case Mvhd(header, data):
								info = data;
							case Trak(header, boxes):
								trak={};
								for(b in boxes)
								{
									switch(b)
									{
										case Tkhd(header, data):
											if(data.width !=0)
											{
												info.width = data.width;
												info.height = data.height;
											}
										case Mdia(header, boxes):
											for(b in boxes)
											{
												switch(b)
												{
													case Mdhd(header, data):
														//trace("------------------------------------"+data);
														//trace(data.timeScale);
														if(data.timeScale!=null)
															trak.timeScale = data.timeScale;
														
													case Minf(header, boxes):
														for(b in boxes)
														{
															switch(b)
															{
																case Stbl(header, boxes):
																	for(b in boxes)
																	{
																		switch(b)
																		{
																			
																			case Stts(header, records):
																				trak.sampleDuration = records[0].sampleDelta;
																				
																			case Stsd(header, records):
																				trak.type = records[0].type=="mp4a"?"audio":"video";
																				
																			case Stsc(header, records):
																				trak.records=records;
																				
																			case Stco(header, entries):
																				trak.chunkType = 32;
																				trak.chunkOffsets = entries;
																				
																			case Co64(header, entries):
																				trak.chunkType = 64;
																				trak.chunkOffsets = entries;
																				
																			case Stsz(header, data):
																				info.videoSamplesTotal = data.entries.length;
																				trak.samples = data;
																			
																			case Stss(header, entries):
																				trak.syncTable = entries;
																			default:
																		}
																	}
																default:
															}
														}
													default:
												}
											}
										default:
									}
								}
								traks.push(trak);
								if(traks.length==2)
									return {info: info, traks:traks};
							default:
						}
					}
				default:
			}
		}
		return {info: info, traks:traks};
	}
	static function createFlvTags(f4v:F4V, inBytes:haxe.io.Bytes)
	{
		var chunkInfos = new Array();
		
		var result = getTraks(f4v);
		var traks = result.traks;
		
		if(traks.length==0)
			throw "no traks found";
		
		var videoTrak = traks[0].type=="video"?traks[0]:traks[1];
		var samplesToChunks : Array<StscRecord> = videoTrak.records;
		var chunkOffsets = videoTrak.chunkOffsets;
		var samples = videoTrak.samples;
		var keyFrames = videoTrak.syncTable;
		//trace(keyFrames);
		var sampleDuration = videoTrak.sampleDuration;
		var timeScale = videoTrak.timeScale;
		
        var tag = 1;
        var pos = null;
		for(i in 0...samplesToChunks.length)
		{
			//trace("i:"+i);
			var record:StscRecord = samplesToChunks[i];
            var firstChunk = record.firstChunk;
            var lastChunk = chunkOffsets.length;
            if (i < samplesToChunks.length - 1) 
			{
				var nextRecord = samplesToChunks[i+1];
                lastChunk = nextRecord.firstChunk-1;
            }
			var j = firstChunk;
			while(j <= lastChunk)
			{
                var sampleCount = record.samplesPerChunk;
                pos = chunkOffsets[j-1];
                while (sampleCount > 0) 
				{
				   var ts:Float = (sampleDuration * (tag - 1)) / timeScale;
                   var size = samples.entries[tag - 1];
				   var chunkInfo = 
				   {
						type:"video",
						offset:pos,
						size:size,
						ts:ts,
						keyFrame:keyFrames==null? false : isKeyFrame(keyFrames, tag)
					}
					chunkInfos.push(chunkInfo);
					pos += size;
                    sampleCount--;
                    tag++;
                }
				j++;
            }
        }
		
		//AUDIO
		var audioTrak = traks[0].type=="audio"?traks[0]:traks[1];
		var samplesToChunks : Array<StscRecord> = audioTrak.records;
		var chunkOffsets = audioTrak.chunkOffsets;
		var samples = audioTrak.samples;
		var sampleDuration =audioTrak.sampleDuration;
		var timeScale = audioTrak.timeScale;

        var tag = 1;
        var pos = null;
		for(i in 0...samplesToChunks.length)
		{
            var record = samplesToChunks[i];
            var firstChunk = record.firstChunk;
            var lastChunk = chunkOffsets.length;
            if (i < samplesToChunks.length - 1) 
			{
                var nextRecord = samplesToChunks[i + 1];
                lastChunk = nextRecord.firstChunk - 1;
            }
			var j = firstChunk;
			while(j<= lastChunk)
			{
                var sampleCount = record.samplesPerChunk;
                pos = chunkOffsets[j - 1];
                while (sampleCount > 0) 
				{
                    var ts = (sampleDuration * (tag - 1)) / timeScale;
                    var size = samples.entries[tag - 1];
					var chunkInfo = 
					{
						type:"audio",
						offset:pos,
						size:size,
						ts:ts,
						keyFrame:true
					}
					chunkInfos.push(chunkInfo);
                    pos += size;
                    sampleCount--;
                    tag++;
                }
				j++;
            }
		}
		chunkInfos.sort(function(i, j) return cast i.ts - j.ts);
		
		//-------------------META-DATA-TAGS---------------------------------
		var info = result.info;
		trace(info);
		var meta=
		{
			duration:info.duration/info.timeScale,//Float,				//Length of the FLV in seconds.
			width:info.width,//Float,
			height:info.height,//Float,
			framerate:(info.videoSamplesTotal * info.timeScale) / info.duration,//Float,
			videodatarate:1.0,//Float,
			audiocodecid:10,//Float,
			videocodecid:7,//Float,	
			canSeekToEnd:true,//Bool,			//Indicates whether the last video frame is a key frame or not. 
			audiodelay:0.0,//Float,
			cuePoints:new Array(),//Array<OnCuePoint>,
		
			//optional
			lasttimestamp:1.0,//Float,
			lastkeyframetimestamp:1.0,//Float,
			metadatacreator:"hxis2"//String,
			//metadatadate:Date.now()//Date
		};
		var metaTag = format.flv.Writer.writeMetaTag(MMeta(meta),0,0,0);
		
		//-------------------VIDEO CONFIG TAG-------------------------------
		var prefix_video_config:Bytes		=	arrayToBytes([0x17, 0x00, 0x00, 0x00, 0x00]);
		var videoDecoderBytes = arrayToBytes([0x01, 0x42, 0x00, 0x14, 0xFF, 0xE1, 0x00, 0x22, 0x67, 0x42, 0x80, 0x14, 0x96, 0x54, 0x0A, 0x0A, 0xD8, 0x0A, 0x84, 0x00, 0x00, 0x0F, 0xA4, 0x00, 0x02, 0xEE, 0x03, 0x81, 0x00, 0x06, 0x1A, 0x80, 0x00, 0x49, 0x3E, 0x3F, 0x18, 0xE0, 0xED, 0x09, 0x13, 0x50, 0x01, 0x00, 0x04, 0x68, 0xCE, 0x35, 0x20, 0x00, 0x00, 0x00, 0x1C]);
		
		var videoConfigBytes = new BytesOutput();
		videoConfigBytes.bigEndian = true;
		videoConfigBytes.write(prefix_video_config);
		//videoConfigBytes.write(videoDecoderBytes);
		var videoConfigTag = FLVVideo(videoConfigBytes.getBytes(), 0);
		
		//-------------------AUDIO CONFIG TAG-------------------------------
		var prefix_audio_config_AAC_Main:Bytes		= arrayToBytes([0xaf, 0x00,    0x11, 0x90, 0x4f, 0x14]);
		var prefix_audio_config_AAC_LC:Bytes		= arrayToBytes([0xaf, 0x00,    0x12, 0x10]);
		var prefix_audio_config_HE_AAC_SBR:Bytes	= arrayToBytes([0xaf, 0x00,    0x13, 0x90, 0x56, 0xe5, 0xa5, 0x48, 0x00]);
		var audioDecoderBytes:Bytes					= arrayToBytes([0x03, 0x80, 0x80, 0x80, 0x21, 0x00, 0x40, 0x10, 0x04, 0x80, 0x80, 0x80, 0x16, 0x40, 0x15, 0x00, 0x06, 0x00, 0x00, 0x02, 0x2F, 0x92, 0x00, 0x01, 0x6C, 0xBF, 0x05, 0x80, 0x80, 0x80, 0x04, 0x11, 0x90, 0xC2, 0x2F, 0x06, 0x01, 0x02, 0x00, 0x00, 0x00, 0x18]);
		
		var audioConfigBytes = new BytesOutput();
		audioConfigBytes.bigEndian = true;
		audioConfigBytes.write(prefix_audio_config_AAC_LC);
		//audioConfigBytes.write(audioDecoderBytes);
		audioConfigBytes.writeByte(0x06);
		var audioConfigTag = FLVAudio(audioConfigBytes.getBytes(), 0);
		
		//-------------------PRE-TAGS---------------------------------------
		var preTags = 	
		[
			metaTag,
			videoConfigTag,
			audioConfigTag
		];
		
		//-----------------------TAGS---------------------------------------
		var tags = [];//[vcf,acf];//new Array();
		var prefix_video_key_frame:Bytes	=	arrayToBytes([0x17, 0x01, 0x00, 0x00, 0x00]);
		var prefix_video_frame:Bytes			=	arrayToBytes([0x27, 0x01, 0x00, 0x00, 0x00]);
		var prefix_audio_frame:Bytes			=	arrayToBytes([0xaf, 0x01]);
		
		var sizeCheck = 0;
		var tagIndex = 0;
		for(chunk in chunkInfos)
		{
			if(false)
			{
				if(chunk.type=="video")
					trace("TAG:"+(++tagIndex)+" , TYPE:"+chunk.type + ", KEY:"+chunk.keyFrame+",\tTS:" + chunk.ts +", MS:"+Math.round(chunk.ts*1000)+", OFFSET:"+chunk.offset+", SIZE:"+chunk.size);
				else if(chunk.type=="audio")
					trace("TAG:"+(++tagIndex)+" , TYPE:"+chunk.type + ", KEY:    ,\tTS:" + chunk.ts +", MS:"+Math.round(chunk.ts*1000)+", OFFSET:"+chunk.offset+", SIZE:"+chunk.size);
			}
			sizeCheck += chunk.size;
			var data = new BytesOutput();
			data.bigEndian = true;
			switch(chunk.type)
			{
				case "video":
					
					data.write( chunk.keyFrame? prefix_video_key_frame : prefix_video_frame );
					data.write( inBytes.sub(chunk.offset, chunk.size) );
					tags.push(FLVVideo( data.getBytes(), Math.round(chunk.ts*1000) ));
					
				case "audio":
					data.write( prefix_audio_frame );
					data.write( inBytes.sub(chunk.offset, chunk.size) );
					tags.push(FLVAudio( data.getBytes(), Math.round(chunk.ts*1000) ));
					
				default:
			};
		}
		//trace(sizeCheck);
		//trace(1226614-8);
	   return {preTags:preTags, tags:tags};
	}
	static function isKeyFrame(keyFrames:Array<Int>, tag:Int):Bool
	{
		for(i in keyFrames)
			if(i==tag)
				return true;
		return false;
	}
	static function arrayToBytes(arr:Array<Int>):haxe.io.Bytes
	{
		var out = new BytesOutput();
		for(i in arr)
			out.writeByte(i);
		return out.getBytes();
	}
}
